package org.beanutopia;

import com.google.common.base.Function;
import static org.beanutopia.function.CompositeFunction.self;
import org.beanutopia.function.FunctionPolicy;
import static org.beanutopia.function.PropertyFunctions.isInstanceOf;
import org.beanutopia.reflect.GetMethod;
import org.beanutopia.reflect.Reflector;
import org.beanutopia.slot.MapSlot;

import java.util.Map;

/**
 * @author ymeymann
 * @since Feb 26, 2007 9:28:08 AM
 */
public abstract class Property<B,V> extends AbstractProperty<B,V> {
  protected static final long serialVersionUID = 0L;
  protected FunctionPolicy<B,V> projection = new FunctionPolicy<B,V>(2);

  public Property() {
    this(null);
  }

  public Property(V defaultValue) {
    super();
    //initProjectionRules();
  }

  @Override
  protected void init() {
    if (!projection.isEmpty()) return;
    resetDefinition();
  }

  @Override protected V get(B object) {
    if (projection == null) {
      return null;
    } else {
//      if (object == null) {
//        Function<?,? extends V> defaultf = projection.getDefault();
//        if (defaultf != null && isNullSupported(defaultf)) {
//          return defaultf.apply(null);
//        } else {
//          return null;
//        }
//      }
      return projection.reduce().apply(object);
    }
  }

  public final Function<? super B, ? extends V> projector() {
    return projection.reduce();
  }

  @SuppressWarnings({"unchecked"})
  protected final void setProjection(Function<? super B, ? extends V> projection) {
    this.projection.clear();
    this.projection.addNullSafetyRule();
    this.projection.addRule(projection);
    this.projection.trimToSize();
  }

  public void define(Function<? super B, ? extends V> projector) {
    setProjection(projector);
  }

  public <C extends B> void redefine(Class<C> cl, Function<? super C, ? extends V> projection) {
    this.projection.addRule(isInstanceOf(cl), self().cast(cl).compose(projection));
  }

  public void resetDefinition() {
    projection.addNullSafetyRule();
    GetMethod<V> getMethod = Reflector.reader(beanType(), name, valueType(), valueType() == Boolean.class ? "is" : "get");
    if (getMethod.supported().apply(beanType())) {
      projection.addRule(getMethod);
    } else {
      projection.addRule(getMethod.supported(), getMethod);
      Function<Map,V> mapper = MapSlot.reader(name());
      if (Map.class.isAssignableFrom(beanType())) {
        projection.addRule(self().cast(Map.class).compose(mapper));
      } else {
        projection.addRule(isInstanceOf(Map.class), self().cast(Map.class).compose(mapper));
      }
    }
    projection.trimToSize();
  }

}
